Skip to content

feat!: react 19 support #2368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
May 14, 2025
Merged

Conversation

dagatsoin
Copy link
Contributor

@dagatsoin dagatsoin commented Apr 18, 2025

This first is up to date from the below discussion so you don't waste time to read all the thread**

--> CURRENT RELEASE: 10.0.0-beta.0 <--

TLDR;

Todo

Why

Support for React 19.

This PR is the last attempt in date to support React 19. It follows the tremendous effort which have been poured into #2363—as a great example of community effort. Kudo kierancap for the work on the Context.

Notable changes

BREAKING CHANGE:

  • update(context)!: <SpringContext ...> is not usable anymore. You need to update your project to use <SpringContextProvider ...>

OTHER CHANGES:

  • chore(native): remove ES export and use 'exports' field in package.json to comply to Metro 0.82
  • chore(peer): accepts react/react-dom from 16.8 to <20
  • chore(peer): update konda and zdog to their react 19 compatible version
  • chore(deps): set demo/ci/docs to react/react-dom 19
  • chore(devDeps): set to react/react-dom 19
  • refactor: impact due to the upgrade to React 19 (linting, typing)

Copy link

changeset-bot bot commented Apr 18, 2025

🦋 Changeset detected

Latest commit: 9f7d1f6

The changes in this PR will be included in the next version bump.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

vercel bot commented Apr 18, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
react-spring ✅ Ready (Inspect) Visit Preview May 14, 2025 2:12pm

@kierancap
Copy link

Hey! Yeah the linting/formatting parts of my PR caused me a bit of a headache too... I did run the formatting script before committing anything, which I would have thought would solve the problem! I guess not!

I would actually say that the underlying problem may be either my individual setup, or the global setup for linting/formatting in the package as a whole. (From personal experience I know how difficult it is to manage it all properly in a massive monorepo).

Do you think it'd be easier to wait for my PR to be merged - then applying the formatting fixes? (I'd actually be interested to see if you got any different results from running the pretter:check/write scripts in my branch)

Realistically if the issue is stemming from a formatting mismatch from either my machine or the monorepo as a whole - Fixing that may help to resolve the problem?

Obviously i'm happy to pull out the core parts of my PR into this PR if that's truly necessary

@dagatsoin
Copy link
Contributor Author

@kierancap thanks for you answer.

After looking back at your PR, it seems that the formatting is a smaller problem since your latest commits. There are way fewer space changes. So this is not a problem anymore.

I rebased your work on my PR so here is a change log. So you have the choice to work on this one or yours.

Fixed:

Use of "any"

We were losing a lot of benefit from TypeScript static typing.

This was caused by the partial changes to the peer and dev dependencies. There are a lot of packages to update including React, React-dom, drei, and three-fiber which prevents the need to "any" all the "Tag" related types.

Using null over undefined

Some "useRef" are initialized with "null" while they were initialized with implicit undefined.

before: useRef()
now: useRef(null)

which is different from useRef(undefined) undefined !== null

To fix:

Potential breaking change on the context.

I rebased the work on jest setup and SprintContext so all the tests pass now.

But there is a catch.

Those changes were made:

(Note : I use ISpringContext for the interface to diff from the SpringContext object.)

The current version exports a context object with the following signature :
type SprintContext = React.Context<ISpringContext> & React.FC<PropsWithChildren<ISpringContext>>

But now it splits the exported context in two parts:
type SpringContextProvider = React.Provider<ISpringContext>
and
type SpringContext = React.Context<ISpringContext>

Given that the context provider was exported at the package level, it will break any project that uses SpringContext.

Ex: If my project uses the old version, switching to this new version will break this code:

import { SpringContet } from 'react-spring';

export myComp = function({children, ...props}) {
  return <SpringContext {...props}>
      {children}
    </SpringContext>
}

So, either we bump to a major version due to the breaking change, or we stay at patch/minor level and find a way to keep the same package interface.

@kierancap
Copy link

@kierancap thanks for you answer.

After looking back at your PR, it seems that the formatting is a smaller problem since your latest commits. There are way fewer space changes. So this is not a problem anymore.

I rebased your work on my PR so here is a change log. So you have the choice to work on this one or yours.

Fixed:

Use of "any"

We were losing a lot of benefit from TypeScript static typing.

This was caused by the partial changes to the peer and dev dependencies. There are a lot of packages to update including React, React-dom, drei, and three-fiber which prevents the need to "any" all the "Tag" related types.

Using null over undefined

Some "useRef" are initialized with "null" while they were initialized with implicit undefined.

before: useRef()

now: useRef(null)

which is different from useRef(undefined) undefined !== null

To fix:

Potential breaking change on the context.

I rebased the work on jest setup and SprintContext so all the tests pass now.

But there is a catch.

Those changes were made:

(Note : I use ISpringContext for the interface to diff from the SpringContext object.)

The current version exports a context object with the following signature :

type SprintContext = React.Context<ISpringContext> & React.FC<PropsWithChildren<ISpringContext>>

But now it splits the exported context in two parts:

type SpringContextProvider = React.Provider<ISpringContext>

and

type SpringContext = React.Context<ISpringContext>

Given that the context provider was exported at the package level, it will break any project that uses SpringContext.

Ex: If my project uses the old version, switching to this new version will break this code:

import { SpringContet } from 'react-spring';



export myComp = function({children, ...props}) {

  return <SpringContext {...props}>

      {children}

    </SpringContext>

}

So, either we bump to a major version due to the breaking change, or we stay at patch/minor level and find a way to keep the same package interface.

Hey!

So...

Any

The any was due to polymorphic components (if i'm remembering right - really annoying). If you were able to tidy those up that's great!

Refs

Completely agreed on the approach to remove undefined from useRef.

Context

I'm nearly certain that with the newest types derived from React, that merging a Provider AND Context initiator into the same object will be nigh on impossible. That's why that old setup was so un-idiomatic and seemed to chip away at the underlying context logic to allow for it to happen.

I would definitely consider a Major bump here - Josh also agreed on the context change in my initial PR.

The Provider paradigm for Context is quite popular these days, and is way simpler on the DX to use. Obviously it does give us a breaking change, but for overall maintainability I think that's a valued change

@dagatsoin dagatsoin changed the title chore: upgrade to react 19 feat: react 19 support Apr 22, 2025
@dagatsoin
Copy link
Contributor Author

dagatsoin commented Apr 22, 2025

@kierancap about the breaking change: indeed, I found the message where @joshuaellis agrees.

How do you want to proceed? Either you cherry pick from mine and I close this one or we continue on this one.

It remains:

  • document the breaking change
  • test with react-native

@dagatsoin dagatsoin changed the title feat: react 19 support feat!: react 19 support Apr 22, 2025
@dagatsoin
Copy link
Contributor Author

Assuming the breaking change, the minimum requirements is now React 19

  • updated react-native to 0.78
  • updated react-konda to 19

@dagatsoin
Copy link
Contributor Author

added a breaking change section in doc

@dagatsoin
Copy link
Contributor Author

fix types error in demo due to mutliple react version:

  • updated styles-component
  • force to use newer version of react-transition-group (dependency of react-select)

@kierancap
Copy link

@kierancap about the breaking change: indeed, I found the message where @joshuaellis agrees.

How do you want to proceed? Either you cherry pick from mine and I close this one or we continue on this one.

It remains:

  • document the breaking change

  • test with react-native

I personally feel like you're making really good progress on this - So if you want to pick up where I left off (full transparency, I won't have much time to keep up with this for the next few weeks). I'm happy for you to continue your work on this one.

I can alert on my PR that we move to this one and I can close my PR.

Would that suit you? If you're happy to adopt my context change (thats the only real major change to the upgrade from my PR - ignoring the test fixes and the useRef init values)

Thank you mate!

(The alternative is that we keep my branch active and you can submit PRs to that branch directly if you wish)

@dagatsoin
Copy link
Contributor Author

@kierancap ok, I can continue from here. I will check your last commit but I think I am up to date.

@dagatsoin dagatsoin marked this pull request as ready for review April 22, 2025 19:56
@@ -57,6 +57,6 @@
"@react-spring/types": "~9.7.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
"react": "^19"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this backwards compatible? I think a lot of other libs have been able to keep that 🤔

Copy link
Contributor Author

@dagatsoin dagatsoin Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshuaellis Yes good catch. I was confused about the breaking change brought by the new SpringContext api.
To confirm, useContext is well supported in React 16.8?

If so, I propose to set
"react": ">=16.8 <20"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is, imo go with the same format we had originally so

    "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for checking and resolve: @joshuaellis

@dagatsoin
Copy link
Contributor Author

dagatsoin commented Apr 23, 2025

Crash on my real life tests on React Native:

import { animated } from '@react-spring/native';

  • passes with React Native 0.76
  • breaks with React Native > 0.78

Cause:

The package @react-spring/native is not resolved with the right path by Metro. Indeed, the ESM package is used which is not compatible with RN (RN uses only CommonJS).

It is due to the Metro breaking change since v0.82, about the "exports" support in package.json.

Metro now uses the exports declaration prior to other entry points. So:

  • on Metro < 0.82 the resolver uses the "main" entry point in the package.json which resolves to the CJS module
  • on Metro >= 0.82 the resolver uses the "exports" entry point in the package.json which resolves to the ESM module

Impact:

  • All RN projects using >= v0.78 will break.

Proposal

To handle that we need to find two entry points in package.json.

EDIT: this proposal has been successfully tested in a client project.

@joshuaellis I have a proposal on this: as far as I know, Metro only uses CommonJS as module system, so:

  • we remove the compilation in ES module in react-spring/native
  • we set the following in targets/native/package.json
"main": "./dist/cjs/index.js",
"types": "./dist/cjs/react-spring_native.development.d.ts",
"exports": {
    "./package.json": "./package.json",
    ".": {
      "types": "./dist/cjs/react-spring_native.development.d.ts",
      "default": "./dist/cjs/index.js"
    }
  },

@@ -6,6 +6,7 @@ export default defineConfig(opts => {
{
entry: 'src/index.ts',
name: 'react-spring_native',
buildFilter: ['development', 'production.min']
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a filter to build only what needed (use case: React Native just needs CJS format)

options: Options
): Options[] => {
const artifactOptions: Options[] = buildTargets.map(
const artifactOptions: Options[] = buildTargets
.filter(target => !buildFilter || buildFilter.includes(target.name))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshuaellis about removing the ESM build for React Native.

I use the filter here but I have an issue. I am new to turbo, so I surely miss something.

Here is a view of my logs during the build. In pink, the log for the native target show that the build are well filtered.
image

But the ESM flavor files still lands in the dist folder.

image

Have you any idea why?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry why do we want to remove ESM from react-native?

Is that what other libraries do? That feels weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshuaellis I explain this here #2368 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solved here @joshuaellis
The esOptions being present, it seems to automatically trigger the ES build regardless of the module resolution context. So I put a condition inside.

Test still passes, no regression detected.

Copy link
Contributor Author

@dagatsoin dagatsoin May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have already build a few pre release versions of my Sproutch UI library including online demos:

@dagatsoin
Copy link
Contributor Author

gentle ping @joshuaellis

@@ -14,14 +14,14 @@
],
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"module": "NodeNext",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we need to get rid of ESM in the native target, we need to handle both ESM and CJS output format. Which is supported by NodeNext.

Otherwise, @react-sping/native definition will not be found at code time and crashes on tests and lint.

Daniel Neveux and others added 19 commits May 14, 2025 14:59
This commit update the context creation to match react 19 types while ensure legacy compatibility.
As this release target React 19, the following commit accept any React version in the 19 range.
BREAKING CHANGE: updating to React 19 brings a breaking change to the SpringContext API. To add the Spring context provider, you must explicitly use SpringContextProvider.

before:
```tsx
import { SpringContext } from 'react-spring'

...
<SpringContext ...>
...
```

now:
```tsx
import { SpringContextProvider } from 'react-spring'

...
<SpringContextProvider ...>
...
```
@joshuaellis joshuaellis force-pushed the chore/upgrade-react-19 branch from 95270cc to b0b9922 Compare May 14, 2025 14:00
joshuaellis
joshuaellis previously approved these changes May 14, 2025
@joshuaellis joshuaellis merged commit 7cc034e into pmndrs:next May 14, 2025
14 checks passed
@joshuaellis joshuaellis mentioned this pull request May 14, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants